//
// Created by haoy on 2017/3/17.
//

#include "rtp_pre_proc.hpp"

#include "mod/h264/h264decoder.h"
#include "mod/h264/h264depacketizer.h"
#include "mod/vp8/vp8depacketizer.h"

#include <h264/h264_nal.hpp>

#define PP_DEBUG(event, fmt) LOG4CPLUS_DEBUG("", "pre-process: " << event << fmt)
#define PP_INFO(event, fmt) LOG4CPLUS_INFO("", "pre-process: " << event << fmt)
#define PP_WARN(event, fmt) LOG4CPLUS_WARN("", "pre-process: " << event << fmt)
#define PP_ERROR(event, fmt) LOG4CPLUS_ERROR("", "pre-process: " << event << fmt)

RTPPreProcess::RTPPreProcess() {
}

RTPPreProcess::~RTPPreProcess() {
    if (_video_depacketizer) {
        delete _video_depacketizer;
        _video_depacketizer = nullptr;
    }
    if (_video_decoder) {
        delete _video_decoder;
        _video_decoder = nullptr;
    }
}

bool RTPPreProcess::hasVideo() {
    return _has_video;
}

bool RTPPreProcess::hasAudio() {
    return _has_audio;
}

mediacodec_id_t RTPPreProcess::getAudioCodec() {
    return _audio_codec;
}

mediacodec_id_t RTPPreProcess::getVideoCodec() {
    return _video_codec;
}

unsigned int RTPPreProcess::getVideoWidth() {
    return _video_width;
}

unsigned int RTPPreProcess::getVideoHeight() {
    return _video_height;
}

unsigned int RTPPreProcess::getAudioSampleRate() {
    return _audio_sample_rate;
}

unsigned int RTPPreProcess::getAudioChannels() {
    return _audio_channels;
}

void RTPPreProcess::onCodecRegistry(CodecRegistry &codecs, uint32_t pt, const std::string& codec_name) {
    _codecs = &codecs;
    PP_INFO("found codec pt mapping", FMT().kv("codec", codec_name)
            .kv("pt", pt)
            .kv("clockrate", codecs.getSampleRateForPT(pt))
            .kv("channels", codecs.getChannelsForPT(pt)));
}

void RTPPreProcess::onAudioPacket(RTPPacket *packet, uint64_t pts) {
    if (_has_audio) {
        return;
    }
    
    if (_audio_codec == MCODEC_ID_UNKNOWN) {
        _audio_codec = (mediacodec_id_t)packet->GetMCodec();
    } else if (_audio_codec != packet->GetMCodec()) {
        PP_ERROR("found more than one audio codec\n", "");
        exit(-1);
    }

    if(_audio_codec > 5) {
        _has_audio = true;
    }

    _audio_sample_rate = _codecs->getSampleRateForPT(packet->GetPayloadType());
    _audio_channels = _codecs->getChannelsForPT(packet->GetPayloadType());
}

#include <iomanip>
#include <sstream>
static std::string hexify(uint8_t* buf, int len) {
    std::ostringstream w;
    for (int i = 0; i < len; ++i) {
        if (i % 16 == 0) {
            w << std::setw(8) << std::setfill('0') << i << ": ";
        }
        w << std::setw(2) << std::setfill('0') << std::hex << int(buf[i]) << " ";

        if (i % 16 == 7) {
            w << "    ";
        }
        if (i % 16 == 15) {
            w << std::endl;
        }
    }
    return w.str();
}

void RTPPreProcess::onVideoPacket(RTPPacket *packet, uint64_t pts) {
    if(_has_video) {
        return ;
    }

    PP_DEBUG("processing", FMT().kv("packet", packet));
    if (packet->GetCodec() != VideoCodec::VP8 && packet->GetCodec() != VideoCodec::H264) {
        dbgd("packet %d ignored: codec %s not supported", packet->GetExtSeqNum(),
             VideoCodec::GetNameFor(VideoCodec::Type(packet->GetCodec())));
        return;
    }

    if (_video_depacketizer == nullptr) {
        switch (packet->GetCodec()) {
            case VideoCodec::VP8:
                _video_depacketizer = new VP8Depacketizer();
                break;
            case VideoCodec::H264:
                _video_depacketizer = new H264Depacketizer();
                break;
        }
    }

    // this check is disabled due to fec presence:
    // when input:
    // 1 vp8 2 fec 3 vp8
    // the seq is not continuous
    // the check in depacketizer (packet->timestamp() != last_timestamp) has same effect
//    if (packet->GetExtSeqNum() != _next_video_extseq && _next_video_extseq != (uint32_t) -1) {
//        PP_DEBUG("reset frame: sequence not continous",
//                 FMT().kv("cur", packet->GetExtSeqNum()).kv("next", _next_video_extseq));
//        _video_depacketizer->ResetFrame();
//    }
//    _next_video_extseq = packet->GetExtSeqNum() + 1;


//    if (packet->GetCodec() == VideoCodec::VP8) {
//        uint8_t *buf = packet->GetMediaData();
//        if (buf[0] >> 4 & 0x1) {
//            _video_depacketizer->ResetFrame();
//        }
//    }

//    if (packet->GetMediaLength() > 16) {
//        static char hex_buf[64];
//        for (int i = 0; i < 16; i++) {
//            sprintf(hex_buf+3*i, "%02X ", packet->GetMediaData()[i]);
//        }
//        hex_buf[48] = '\0';
//        PP_WARN("packet",FMT().kv("buf", std::string(hex_buf)));
//    }

    MediaFrame *frame = _video_depacketizer->AddPacket(packet);
    if (!frame) {
        return;
    }
    if (frame->GetLength() == 0) {
        return;
    }

    if (_video_codec == MCODEC_ID_UNKNOWN) {
        _video_codec = (mediacodec_id_t)packet->GetMCodec();
    } else if (_video_codec != packet->GetMCodec()) {
        PP_ERROR("found more than one video codec\n", "");
        exit(-1);
    }

    // 2. decode frame
    // packet has same codec to frame
    switch (packet->GetCodec()) {
        case VideoCodec::VP8: {
            if (!_vpx_codec_inited) {
                vpx_codec_err_t err = vpx_codec_dec_init(&_vpx_codec_ctx, vpx_codec_vp8_dx(), NULL, 0);
                if (err != VPX_CODEC_OK) {
                    PP_ERROR("vpx_codec_dec_init error",
                             FMT().kv("err", err).kv("cause", vpx_codec_err_to_string(err)));
                    exit(-1);
                } else {
                    _vpx_codec_inited = true;
                }
            }
//            if (frame->GetLength() > 10) {
//                static char hex_buf[64];
//                for (int i = 0; i < 10; i++) {
//                    sprintf(hex_buf+2*i, "%02X ", frame->GetData()[i]);
//                }
//                hex_buf[30] = '\0';
//                PP_WARN("frame",FMT().kv("buf", std::string(hex_buf)));
//            }

            vpx_codec_err_t err = vpx_codec_decode(&_vpx_codec_ctx, frame->GetData(), frame->GetLength(), NULL, 0);
            if (err != VPX_CODEC_OK) {
                PP_ERROR("vpx_codec_decode err", FMT().kv("err", err).kv("cause", vpx_codec_err_to_string(err)));
            } else {
                vpx_codec_iter_t iter = NULL;
                vpx_image_t *img = vpx_codec_get_frame(&_vpx_codec_ctx, &iter);
                if (!img) {
                    PP_INFO("no image", "");
                }

                //Get dimensions
                uint32_t width = img->d_w;
                uint32_t height = img->d_h;
                _video_width = std::max(_video_width, width);
                _video_height = std::max(_video_height, height);
                _has_video = true;
            }
        }
        break;
        case VideoCodec::H264: {
            if (!_video_decoder) {
                _video_decoder = new H264Decoder();
            }
            //dbgd("pre-proc: decoding following bytes:\n%s", hexify(frame->GetData(), frame->GetLength()).c_str());
            int decoded = _video_decoder->Decode(frame->GetData(), frame->GetLength());
            if (decoded == 1) {
                int width = _video_decoder->GetWidth();
                int height = _video_decoder->GetHeight();
                dbgd("pre-proc: decoded h264 %s frame %dx%d", (_video_decoder->IsKeyFrame() ? "I" : "non-I"), width,
                     height);
                _video_width = std::max(_video_width, (uint32_t) width);
                _video_height = std::max(_video_height, (uint32_t) height);
                _has_video = true;
            }
        }
        break;
    }

    return;
}